/*
 *  shnodecontainerwidget_qt.cpp
 *  shaderz_v3
 *
 *  Created by Anjali on 4/19/11.
 *  Copyright 2011 __MyCompanyName__. All rights reserved.
 *
 */

#include <QtGui/QMouseEvent>
#include <QtGui/QMenu>
#include <QtGui/QPainterPath>
#include <QtGui/QPainter>

#include "shnode_math.h"
#include "shnode_color.h"
#include "shnodecontainerwidget_qt.h"
#include "shnodeui_qt.h"
#include "shnodeuicolor_qt.h"

using namespace shaderz;
using namespace shaderzui::qt;

shNodeContainerWidget::shNodeContainerWidget( QWidget* InParent )
	: ParentClass( InParent )
{
	mCurrentlySelectedStubInfo.first = 0;
	mCurrentlySelectedStubInfo.second = 0;
	
	setMouseTracking(true);
	
	mActionList.reserve( SHNT_MAX );
	mActionList.assign( (size_t)SHNT_MAX, 0 );
	
	QAction* pNewAction = 0;
	
#define DEF_ACTION( Name, List, SlotName ) \
	pNewAction = new QAction( #Name, this); \
	pNewAction->setText( #Name );	\
	connect( pNewAction, SIGNAL(triggered()), this, SLOT(SlotName()) ); \
	List[ SHNT_##Name ] = pNewAction;
	
	DEF_ACTION( CONST,		mActionList, onCreateConstantNode );
	DEF_ACTION( MATH_ADD,	mActionList, onCreateMathAddNode );
	DEF_ACTION( MATH_SUB,	mActionList, onCreateMathSubNode );
	DEF_ACTION( MATH_MUL,	mActionList, onCreateMathMulNode );
	DEF_ACTION( MATH_DIV,	mActionList, onCreateMathDivNode );
	DEF_ACTION( RGBA,		mActionList, onCreateRGBANode );
	
#undef DEF_ACTION
}

void shNodeContainerWidget::onNodeDestroyed( unsigned int InNodeCoreAddress )
{
	// if the destroyed node contains the current selection, clear it too.
	if( mCurrentlySelectedStubInfo.first == mNodeToUiMap[ (shNode*)InNodeCoreAddress ] )
	{
		mCurrentlySelectedStubInfo.first = 0;
		mCurrentlySelectedStubInfo.second = 0;
	}

	// remove the node to be destroyed from the hash maps
	mNodeToUiMap.erase( (shNode*)(InNodeCoreAddress) );
}

void shNodeContainerWidget::mouseMoveEvent( QMouseEvent* InEventInfo )
{
	ParentClass::mouseMoveEvent(InEventInfo);
	update();
}

void shNodeContainerWidget::mouseReleaseEvent( QMouseEvent* InEventInfo )
{
	ParentClass::mouseReleaseEvent(InEventInfo);
	
	if( InEventInfo->button() == Qt::RightButton )
	{
		QMenu* pMenu = generateRClickContextMenu();
		pMenu->popup( InEventInfo->globalPos() );
	}
	// releasing the left button will cause the partial link to snap or connect to the widget under it
	else if( InEventInfo->button() == Qt::LeftButton )
	{
		mCurrentlySelectedStubInfo.first = 0;
		mCurrentlySelectedStubInfo.second = 0;
		update();
	}
}

QMenu* shNodeContainerWidget::generateRClickContextMenu()
{
	QMenu* generatedMenu = new QMenu(this);

	// create const node
	generatedMenu->addAction( mActionList[ SHNT_CONST ] );	
	
	generatedMenu->addSeparator();
	generatedMenu->addAction( mActionList[ SHNT_MATH_ADD ] );
	generatedMenu->addAction( mActionList[ SHNT_MATH_SUB ] );
	generatedMenu->addAction( mActionList[ SHNT_MATH_MUL ] );
	generatedMenu->addAction( mActionList[ SHNT_MATH_DIV ] );
	generatedMenu->addSeparator();
	generatedMenu->addAction( mActionList[ SHNT_RGBA ] );
	
	return generatedMenu;
}

void shNodeContainerWidget::onCreateConstantNode()
{
	float constVal = 1.0f;
	shNodeConstant* pConstNode = new shNodeConstant("TestConstantNode", &constVal);
	shNodeUI* pNodeUI = new shNodeUI( this, pConstNode );
	pNodeUI->setGeometry( QCursor::pos().x(), QCursor::pos().y(), pNodeUI->width(), pNodeUI->height() );
	pNodeUI->show();
	
	// connect the movement signal to the update slot so that the contianer is refreshed every time a widget is moved
	connect( pNodeUI, SIGNAL(nodeUIMoved()), this, SLOT(update()) );
	connect( pNodeUI, SIGNAL(nodeUIDeleted(unsigned int)), this, SLOT(onNodeDestroyed(unsigned int)) );
	connect( pNodeUI, SIGNAL(stubInteraction(shStubInteraction, linkStubUI*, shNodeUI*)), this, SLOT(onStubInteraction(shStubInteraction, linkStubUI*, shNodeUI*)) );				
	
	mNodeToUiMap[pConstNode] = pNodeUI;
}

void shNodeContainerWidget::onCreateMathAddNode()
{
	shNodeMath* pMathNode = new shNodeMath( "TestMathNode", shNodeMath::SHMO_Add );
	pMathNode->addInputStub( shNode::constructLinkStub("A", shNode::SHIO_INPUT, shNode::SHST_CONST1VEC) );
	pMathNode->addInputStub( shNode::constructLinkStub("B", shNode::SHIO_INPUT, shNode::SHST_CONST1VEC) );
	pMathNode->addOutputStub( shNode::constructLinkStub("Result", shNode::SHIO_OUTPUT, shNode::SHST_CONST1VEC) );

	shNodeUI* pNodeUI = new shNodeUI( this, pMathNode );
	pNodeUI->setGeometry( QCursor::pos().x(), QCursor::pos().y(), pNodeUI->width(), pNodeUI->height() );
	pNodeUI->show();
	
	// connect the movement signal to the update slot so that the contianer is refreshed every time a widget is moved
	connect( pNodeUI, SIGNAL(nodeUIMoved()), this, SLOT(update()) );
	connect( pNodeUI, SIGNAL(nodeUIDeleted(unsigned int)), this, SLOT(onNodeDestroyed(unsigned int)) );
	connect( pNodeUI, SIGNAL(stubInteraction(shStubInteraction, linkStubUI*, shNodeUI*)), this, SLOT(onStubInteraction(shStubInteraction, linkStubUI*, shNodeUI*)) );				
		
	mNodeToUiMap[pMathNode] = pNodeUI;
}

void shNodeContainerWidget::onCreateMathSubNode()
{
}

void shNodeContainerWidget::onCreateMathMulNode()
{
}

void shNodeContainerWidget::onCreateMathDivNode()
{
}

void shNodeContainerWidget::onCreateRGBANode()
{
	shNodeColor* pColorNode = new shNodeColor("ColorNode", 0.0f, 0.0f, 255.0f, 255.0f);
	shNodeUI_color* pNodeUI = new shNodeUI_color( this, pColorNode );
	pNodeUI->setGeometry( QCursor::pos().x(), QCursor::pos().y(), pNodeUI->width(), pNodeUI->height() );
	pNodeUI->show();
	
	// connect the movement signal to the update slot so that the contianer is refreshed every time a widget is moved
	connect( pNodeUI, SIGNAL(nodeUIMoved()), this, SLOT(update()) );
	connect( pNodeUI, SIGNAL(nodeUIDeleted(unsigned int)), this, SLOT(onNodeDestroyed(unsigned int)) );
	connect( pNodeUI, SIGNAL(stubInteraction(shStubInteraction, linkStubUI*, shNodeUI*)), this, SLOT(onStubInteraction(shStubInteraction, linkStubUI*, shNodeUI*)) );				
	
	mNodeToUiMap[pColorNode] = pNodeUI;
}

void shNodeContainerWidget::paintEvent( QPaintEvent* InEventInfo )
{
	ParentClass::paintEvent( InEventInfo );

	// the painting canvas
	QPainterPath painterPath;

	// draw the connections between the nodes
	std::map<shNode*, shNodeUI*>::iterator nodeItr;
	for( nodeItr = mNodeToUiMap.begin(); nodeItr != mNodeToUiMap.end(); ++nodeItr )
	{
		std::pair<shNode*, shNodeUI*>	mapEntry = *nodeItr;
		drawHelperNodeConnections( mapEntry.first, painterPath );
	}
	
	// if we are dragging a new link, draw that too
	if( mCurrentlySelectedStubInfo.first )
	{
		QPoint startPoint = mapFromGlobal( mCurrentlySelectedStubInfo.second->mapToGlobal(mCurrentlySelectedStubInfo.second->getStubPos()) );
											
		QPoint endPoint = mapFromGlobal(QCursor::pos());
		
		int sign = (mCurrentlySelectedStubInfo.second->getAlignMode() == linkStubUI::STUBALIGN_LEFT ) ? -1 : 1;
		QPoint ctrlPt1 = startPoint + QPoint( sign * 120, 0 );
		QPoint ctrlPt2 = endPoint + QPoint( -sign * 120, 0 );
		
		painterPath.moveTo( startPoint );
		painterPath.cubicTo( ctrlPt1, ctrlPt2, endPoint );
	}
	
	// finally render the drawings on the painter path canves
	QPainter painter(this);
	painter.drawPath(painterPath);
}

void shNodeContainerWidget::drawHelperNodeConnections( shNode* InNode, QPainterPath& InPainterPath ) 
{
	if( InNode )
	{
		// draw a connection starting from the output of each node to the input of the other node
		std::vector<shNode::linkStub*>& outputStubList = InNode->outputStubs();
		for( int opIndex=0; opIndex < outputStubList.size(); ++opIndex )
		{
			// now draw links from each stub to the other point of the connection
			for( int linkIndex=0; linkIndex < outputStubList[opIndex]->allLinks.size(); ++linkIndex )
			{
				shNode::link& linkInfo = outputStubList[opIndex]->allLinks[linkIndex];
				shNodeUI* startPointNodeUI = mNodeToUiMap[InNode];
				
				linkStubUI* startPointStubUI = startPointNodeUI->findUIForStub( outputStubList[opIndex] );
				QPoint startPoint = mapFromGlobal(startPointStubUI->mapToGlobal( startPointStubUI->getStubPos() ));
				
				shNodeUI* endPointNodeUI = mNodeToUiMap[ linkInfo.otherNode ];
				linkStubUI* endPointStubUI = endPointNodeUI->findUIForStub( linkInfo.otherNode->findStub(linkInfo.otherStubName) );
				QPoint endPoint = mapFromGlobal(endPointStubUI->mapToGlobal( endPointStubUI->getStubPos() ));
				
				// get the two control points for the cubic bezier curve
				QPoint ctrl1 = startPoint + QPoint( 120, 0 );
				QPoint ctrl2 = endPoint + QPoint( -120, 0 );
				
				InPainterPath.moveTo( startPoint );
				InPainterPath.cubicTo( ctrl1, ctrl2, endPoint );
			}
		}
	}
}

void shNodeContainerWidget::onStubInteraction( shStubInteraction InInteraction, linkStubUI* InStubUI, shNodeUI* InNodeUI )
{
	switch( InInteraction )
	{
	case SHSI_MousePressed:
		mCurrentlySelectedStubInfo.first = InNodeUI;
		mCurrentlySelectedStubInfo.second = InStubUI;
		break;
		
	case SHSI_MouseMove:
		update();
		break;
		
	case SHSI_MouseReleased:
		// todo: see if we are over any stub... try connecting to it.
		mCurrentlySelectedStubInfo.first = 0;
		mCurrentlySelectedStubInfo.second = 0;
		update();
		break;
	}
}